﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Web.Mvc;

using Mvc.XForms.Controls.Settings;
using Mvc.XForms.Utilities;

namespace Mvc.XForms.Controls {
	public class Select<TContext, T> : CoreControlBase<TContext>, ISelect {
		private Expression<Func<TContext, IEnumerable<T>>> bind;
		private IEnumerable<T> items;
		private Func<T, object> valueSelector;
		private Func<T, object> labelSelector;
		private IList<SelectListItem> selectList;

		public SelectSettings Settings { get; private set; }
		public string ValueSelector { get; set; }

		public override XFormControlType ControlType {
			get { return XFormControlType.Select; }
		}

		public Select(IContainerControl<TContext> parent, Expression<Func<TContext, IEnumerable<T>>> bind, IEnumerable<T> items, Expression<Func<T, object>> valueSelector, Func<T, object> labelSelector)
			: base(parent, bind.Body) {
			if (items == null)
				throw new ArgumentNullException("items", string.Format("Select items for binding \"{0}\" cannot be null", bind));

			Settings = new SelectSettings(this);
			CssClass.Add("select");
			Appearance = Appearance.Minimal;

			this.bind = bind;
			this.items = items;
			this.valueSelector = valueSelector.Compile();
			this.labelSelector = labelSelector;

			ValueSelector = valueSelector.Body.ExtractID();
		}

		public override string Render() {
			Label = Label ?? bind.Body.ExtractMember().CapitalizeSpace();

			if (Appearance == Appearance.Full) {
				CssClass.Add("full");
				return XForm.Formatter.SelectFull(this);
			}

			return XForm.Formatter.Select(this);
		}

		public IList<SelectListItem> SelectList {
			get {
				if (selectList == null) {
					var selectedValues = Value as List<string>;
					selectList = new List<SelectListItem>();

					this.items.ForEach(t => {
						var listItem = new SelectListItem();

						var valueStr = valueSelector(t);
						listItem.Value = valueStr == null ? string.Empty : valueStr.ToString();

						var labelStr = labelSelector(t);
						listItem.Text = labelStr == null ? string.Empty : labelStr.ToString();

						listItem.Selected = selectedValues.Contains(listItem.Value);
						selectList.Add(listItem);
					});
				}

				return selectList;
			}
		}

		public override void SetValueFromRequest() {
			if (XForm.FormCollection[ID] != null) {
				// First one is hidden field (required because how do we differentiate between
				// control not on page and no options selected?)
				Value = XForm.FormCollection.GetValues(ID).Skip(1).ToList();
			}
		}

		protected override Func<object> SetValueAction {
			get {
				return () => {
					var selectModelValues = bind.Compile()(Parent.ModelContext);
					if (selectModelValues != null) {
						if (!(selectModelValues is IEnumerable<T>))
							throw new Exception(string.Format("Bind must point to a IEnumerable<{0}> for Select (ID = {1})", typeof(T), ID));

						return (selectModelValues as IEnumerable<T>).Select(o => valueSelector(o).ToString()).ToList();
					} else {
						return new List<string>();
					}
				};
			}
		}

		public static IList<T> GetEnumList(Expression<Func<TContext, IEnumerable<T>>> enumBind) {
			IList<T> enums;
			if (!EnumHelper.TryGetEnumList(out enums))
				throw new ArgumentException(string.Format("Enum bind ({0}) must point to an Enum or Nullable Enum", enumBind));

			return enums;
		}
	}
}